-- commonUtil
-- created by panyl
-- 通用接口

-- 最大的整数
MAX_VALUE = 2147483647;

-- 一天秒数
SECONDS_PER_DAY = 86400;

-- 相当于服务器的@TRUE
TRUE = 1

-- 是否同一个月
function isSameMonth(time1, time2, offset)
    -- 偏移时间
    offset = offset or 0

    local tab1 = os.date("*t", time1 - offset);
    local tab2 = os.date("*t", time2 - offset);

    -- 不是同年或同月的
    if  tab1.month  ~= tab2.month or
        tab1.year   ~= tab2.year then
        return false
    end

    return true
end

-- 是否同一天
function isSameDay(time1, time2, offset)
    -- 偏移时间
    offset = offset or 0

    local tab1 = os.date("*t", math.max(time1 - offset, 0));
    local tab2 = os.date("*t", math.max(time2 - offset, 0));

    -- 不是同一天
    if  tab1.day    ~= tab2.day or
        tab1.month  ~= tab2.month or
        tab1.year   ~= tab2.year then
        return false
    end

    return true
end

-- 判断某年是否是闰年
function isLeapYear(year)
    if year % 100 == 0 then
        if year % 400 == 0 then
            return true;
        end
    elseif year % 4 == 0 then
        return true;
    else
        return false;
    end
end

-- 获取本月有几天
function getMonthDays(mon)
    local tab = os.date("*t");
    if not mon then
        -- 如果没有传入月份，默认取本月
        mon = tab.month;
    end

    assert(type(mon) == "number");

    if mon < 1 or mon > 12 then
        error("月份错误。");
        return 0;
    end

    if mon == 1 or mon == 3 or mon == 5 or mon == 7 or mon == 8 or mon == 10 or mon == 12 then
        return 31;
    elseif mon == 4 or mon == 6 or mon == 9 or mon == 11 then
        return 30;
    else
        return iif(isLeapYear(tab.year), 29, 28);
    end
end

-----------------------
-- 获取今天是星期几
-- @param tm  系统的日历时间（默认取服务器当前时间）
-- @return number
function dayOfWeek(tm)
    -- [0-6 = Sunday-Saturday]
    tm = tm or TimeM.getServerTime();
    local day = tonumber(os.date("%w", tm));
    if day == 0 then
        return 7;
    end
    return day;
end

-- 获取一个唯一数，严格递增
function getUniqueNumber()
    local id = _G.uniqueNumber or 0;

    _G.uniqueNumber = id + 1;

    return id + 1;
end

------------------------------
-- 根据随机种子抽取位置
-- @param weight   权重列表，格式为： { 10,20, ... }
-- @param seed     随机种子
-- @return 返回被选中的位置
function randomSelectBySeed(weight, seed)
    -- 权重求和
    local sum = 0;
    for _, value in pairs(weight) do
        sum = sum + value;
    end

    -- 将随机种子控制在区间内
    seed = seed % sum;

    local acc = 0;
    for index, value in pairs(weight) do
        acc = acc + value;
        if seed <= acc then
            -- 随机种子落在该区间，返回位置
            return index;
        end
    end
end

------------------------------
-- 随机抽取若干个元素（等权重）
-- @param   list        待抽取的table
-- @param   amount      抽取的元素个数，不可重复时，不超过list的长度
-- @param   duplicate   是否可重复，默认不可重复
-- @param   filterList  排除的列表
-- @return  返回抽取出的元素
function randomSelectMulti(list, amount, duplicate, filterList)
    local elemAmount = amount;
    if duplicate ~= true then
        -- 不可重复时，不超过list的长度
        -- 默认不可重复
        elemAmount = math.min(amount, #list);
    end

    -- 抽取元素
    local elemList = table.deepcopy(list);

    -- 先排除不需要抽取的元素
    if fileterList ~= nil then
        for _, i in pairs(filterList) do
            local filterIndex = table.indexOf(elemList, i);
            if filterIndex ~= -1 then
                table.remove(elemList, filterIndex);
            end
        end
    end

    local getList = { };
    for i = 1, elemAmount do
        local index = math.random(1, #elemList);
        table.insert(getList, elemList[index]);

        if duplicate ~= true then
            table.remove(elemList, index);
        end
    end

    return getList;
end

------------------------------
-- 随机抽取一个选项
-- @param arr    候选列表（每个选项必须配置ratio字段）
-- @param seed   随机种子
-- @param return 返回一个选项
function fetchElemBySeed(arr, seed)
    local sum = 0;

    -- 所有的权和
    for index = 1, #arr do
        if arr[index].ratio == -1 then
            -- 配置了必定抽取，直接返回
            return arr[index];
        end

        sum = sum + arr[index].ratio;
    end

    -- 将随机数控制在区间内
    seed = seed % sum;
    sum = 0;

    -- 随机数落到哪个商品的区间内就选哪个
    for index = 1, #arr do
        sum = sum + arr[index].ratio;

        if sum > seed then
            return arr[index];
        end
    end

    -- 没抽到，默认返回数组的第一个
    return arr[1];
end

------------------------------
-- 随机抽取一个选项
-- @param arr   候选列表（每个选项必须配置ratio字段）
-- @param seed  随机种子
-- @return 返回一个选项的索引
function fetchElemBySeed2(arr, seed)
    local sum = 0;

    -- 所有的权和
    for index = 1, #arr do
        if arr[index].ratio == -1 then
            -- 配置了必定抽取，直接返回
            return index;
        end

        sum = sum + arr[index].ratio;
    end

    -- 将随机数控制在区间内
    seed = seed % sum;
    sum = 0;

    -- 随机数落到哪个商品的区间内就选哪个
    for index = 1, #arr do
        sum = sum + arr[index].ratio;

        if sum > seed then
            return index;
        end
    end

    -- 没抽到，默认返回数组的第一个
    return 1;
end

-- 根据随机种子抽取多个选项
-- @param arr       候选列表（每个选项必须配置ratio字段）
-- @param seeds     种子列表（种子的个数即需要抽取的选项个数）
-- @param dupicate  抽取的选项是否可重复（默认不可重复）
function fetchElemsBySeeds(arr, seeds, dupicate)
    assert(type(arr) == "table");
    assert(type(seeds) == "table");
    assert(#seeds <= #arr);

    if #arr == 0 or # seeds == 0 then
        return {};
    end

    dupicate = dupicate or false;

    local ret = {};

    -- 可重复
    if dupicate then
        for _, seed in pairs(seeds) do
            table.insert(ret, fetchElemBySeed(arr, seed));
        end
    else
        -- 不可重复
        local tempArr = table.add({}, arr);
        for _, seed in pairs(seeds) do
            local index = fetchElemBySeed2(tempArr, seed);
            table.insert(ret, tempArr[index]);

            -- 剔除已被抽取的元素
            table.remove(tempArr, index);
        end
    end

    return ret;
end

-- 计算两点组成的线段与水平方向的夹角
function calcPointAngle(fromPos, toPos)
    local deltalX = toPos.x - fromPos.x;
    local deltalY = toPos.y - fromPos.y;

    -- 计算两点间的距离
    local distance = cc.pGetDistance(fromPos, toPos);

    -- 计算旋转角度
    local angle;
    if toPos.y > fromPos.y then
        -- 第一、二象限
        angle = math.acos(deltalX / distance);
    else
        -- 第三、四象限
        angle = 2*math.pi - (math.acos(deltalX / distance));
    end

    -- 将弧度转成度
    angle = radiansToDegrees(angle);
    return angle;
end

-- 取最近的一个位置
function findRecentDungeonPos(pos, arr)
    if table.indexOf(arr, pos) > 0 then
        return pos;
    end

    local coor = DungeonM.convertToCoor(pos);
    local targetPos;
    local distance = MAX_VALUE;
    local axb = -1;

    for _, p in pairs(arr) do
        local c = DungeonM.convertToCoor(p);
        local dx = math.abs(coor.x - c.x);
        local dy = math.abs(coor.y - c.y);
        local daxb = dx * dy;
        local d = dx + dy;

        if not targetPos or d < distance or
            d == distance and axb < daxb then
            targetPos = p;
            distance = d;

            axb = daxb;
        end
    end

    return targetPos;
end

-- 计算距离
function calcGridDistance(pos, targetPos)
    local coor = DungeonM.convertToCoor(pos);
    local c = DungeonM.convertToCoor(targetPos);
    local dx = math.abs(coor.x - c.x);
    local dy = math.abs(coor.y - c.y);
    local d = dx + dy;

    return d;
end

-- 按照远近排序
function sortRecentDungeonPos(pos, arr)
    local ret = table.deepcopy(arr);
    table.sort(ret, function(pos1, pos2) return calcGridDistance(pos1, pos) * 100 + pos1 < calcGridDistance(pos2, pos) * 100 + pos2; end);
    return ret;
end

-- 逻辑层提示
function doAlert(msg)
    print("重要消息：" .. msg);
    MSG.addMsg(MSG.getMsgId("MSG_DIALOG_OK"), {["msg"]=msg,});
end

-- 判断value是否在tbl中
function IsInTable(value, tbl)
    for k,v in ipairs(tbl) do
        if v == value then
            return true;
        end
    end

    return false;
end

-- 获取当前所在的区组id
function getCurrentServerId()
    local serverId;
    if not _G.aaaAccountInfo then
        serverId = DEFAULT_AAA_ID;
    else
        serverId = _G.aaaAccountInfo.AAA_ID or DEFAULT_AAA_ID;
    end

    return math.ceil(serverId);
end

-- 获取当前所在区组名称
function getCurrentServerName()
    -- 目前是全球同服的，返回一个区组名称即可
    return getLocStr("my_server_name");
end

-- 一个比较精确的向上取整
-- 分子(numerator)、分母(denominator)都不能太大，要确保下面的四则运算不会溢出
-- 对应服务器的exact_ceil方法
function extraCeil(numerator, denominator)
    assert(type(numerator) == "number" and type(denominator) == "number", "not number");

    local ret = math.floor(numerator / denominator);

    if numerator > ret * denominator then
        ret = ret + 1;
    end

    return ret;
end

-------------------------
-- 返回阿拉伯数字对应的罗马数字
-- @param num : 阿拉伯数字
function getRomanNumeral(num)
    local str = "";
    local symbol = {"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
    local value = {1000,900,500,400, 100, 90,  50, 40,  10, 9,   5,  4,   1};

    local i = 1;
    while(num ~= 0)
    do
        while(num>=value[i])
        do
            num = num - value[i];
            str = str .. symbol[i];
        end

        i = i + 1;
    end
    return str;
end

-- 筛选出套装，equips是以整数为key（不一定连续）
function filterSuit(equips)
    local ret = {};
    local filter = {};
    local keys = table.keys(equips);
    local total = #keys;

    for i = 1, total do
        repeat
            local index = keys[i];
            -- 如果已经被选过了
            if filter[index] then
                break;
            end

            local classId = equips[index];
            local suitId = EquipM.query(classId, "suit") or 0;
            local tempFilter = {[classId] = index, };

            -- 不是套装
            if suitId <= 0 then
                break;
            end

            for j = i + 1, total do
                local index = keys[j];
                -- 同一套装且未被筛选过
                if EquipM.query(equips[index], "suit") == suitId and
                    not tempFilter[equips[index]] and not filter[index] then
                    tempFilter[equips[index]] = index;
                end
            end

            local arr = table.values(tempFilter);
            local wholeSuit = (#arr == #SuitM.query(suitId, "active"));
            for _, index in pairs(arr) do
                -- 不管是否满足一整套，都应该加入被筛选过的列表
                filter[index] = true;

                -- 如果满足一整套
                if wholeSuit then
                    ret[index] = suitId;
                end
            end

        until true;
    end

    return ret;
end

-- 把一串奖励拼接成字符
function spliceBonusToTip(list)
    if #list <= 0 then
        return "";
    end

    local arr = list;
    if type(arr[1]) ~= "table" then
        arr = { list, };
    end

    local ret = {};
    for _, bonus in pairs(arr) do
        repeat
            if bonus[3] <= 0 then
                break;
            end

            if bonus[1] == 1 then
                -- 物品
                table.insert(ret, string.format(getLocStr("item_bonus_format"), ItemM.query(bonus[2], "name"), bonus[3]));
            elseif bonus[1] == 2 then
                -- 属性
                table.insert(ret, string.format(getLocStr("item_bonus_format"), FieldsM.getFieldName(bonus[2]) or "", bonus[3]));
            end
        until true;
    end

    return string.implode(ret, getLocStr("pause_mark"));
end

-- 获取文件的后缀名
-- @param fileName 带后缀的文件名，支持路径
function getFileExtension(fileName)
    -- 使用正则表达式
    return fileName:match(".+%.(%w+)$");
end

-- 获取文件的名字
-- @param fileName 文件名
function getFieldNameWithoutExten(fileName)
    local str = fileName:match(".+()%.%w+$");
    return (nil ~= str) and fileName:sub(1, str - 1) or fileName;
end

-- 将数字转化为千分法描述的字符串
-- 如 1234567 转化为 1,234,567
function thousandNum(number)
    if type(number) ~= "number" then
        return number;
    end

    local ret;

    repeat
        local num = number % 1000;
        number = (number - num) / 1000;
        local str = string.format("%03d", num);

        if number <= 0 then
            str = tostring(num);
        end

        if ret then
            ret = str .. "," .. ret;
        else
            ret = str;
        end
    until number <= 0;

    return ret;
end

-- 验证客户端AutoPatch不载入，另外写一个
function errorTraceback(msg)
    -- 定义局部变量保存msg
    local message = msg;

    local last_traceback = "";
    last_traceback = last_traceback .. "------------" .. os.date() .. "----------\n";
    last_traceback = last_traceback .. "LUA ERROR: " .. tostring(msg) .. "\n";
    last_traceback = last_traceback .. debug.traceback();
    last_traceback = last_traceback.. "\n----------------------------------------\n";

    return last_traceback;
end

-- 复制到剪贴板
function copyToClipBoard(msg, tip)
    cc.Device:copyToClipboard(msg);
    if tip then
        doAlert(tip);
    else
        doAlert(getLocStr("text_copied_default"));
    end
end

-- 打印lua内存占用
function printLuaMemSize(tag)
    print("["..tag.."]lua mem size:"..math.floor(collectgarbage("count")).."K");
end

---------------------
--@根据语言版本拼接字符串
--@param string1    第一个字符串
--@param string2    第二个字符串
--...
--@param stringn    第n个字符串
function spliceLocStr(...)
    local spacingSymbol = getLocStr("spacing_symbol");
    local locStr = "";

    for i, str in pairs{...} do
        if type(str) ~= "string" and type(str) ~= "number" then
            -- 只支持字符串和数字
            locStr = locStr .. "";
        else
            if i == 1 then
                locStr = str;
            else
                locStr = locStr .. spacingSymbol .. str;
            end
        end
    end

    return locStr;
end

---------------------
--@获取等级拼接字符串
--@param level   等级
function getLevelStr(level)
    return string.format(getLocStr("level_flag"), level);
end

-- 重新启动app
-- （实质为重置lua_state，使其重新加载AutoPath.lua）
function rebootApp()
    Communicate.reset();
    cc.ScriptEngineManager:getInstance():reset();
end

-- 是否IPV4地址
function isIpv4(str)
    local list = string.explode(str, ".");
    if #list ~= 4 then
        return false;
    end

    for _, p in ipairs(list) do
        if sys_tonumber(p) == nil then
            return false;
        end
    end

    return true;
end

-- 昵称
function getNickName(who)
    local nickname = who;
    if type(who) == "table" then
        nickname = who.dbase:query("nickname");
    end

    if type(nickname) ~= "string" then
        return nickname or "";
    end

    -- 如果不是以:b64:开头的，直接返回
    local prefix = ":b64:";
    if not string.startWith(nickname, prefix) then
        return nickname;
    end

    name = string.sub(nickname, string.len(prefix) + 1)

    return MyBase64.decode(name);
end

